Volume Renderer v2#1120
Open
ElpadoCan wants to merge 37 commits into
Open
Conversation
This was referenced Jun 15, 2026
Closed
…_ACDC into renderer_3d_v2
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a new VisPy-based 3D volume rendering window (VolumeRendererWindow) with multi-channel LUT/opacity controls, segmentation-mask overlay, and optional points layers, and integrates it into the main Cell-ACDC GUI via a “Launch 3D viewer” action/button.
Changes:
- Added a new 3D volume-renderer module (canvas + toolbar widgets) with blending utilities and voxel scaling helpers.
- Extended colormap/gradient and points-layer UI infrastructure (PyQtGraph↔VisPy symbol mapping, gradient shuffle actions, points appearance dialog support).
- Wired the renderer into the GUI and added demo scripts + data downloader for quick testing.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
cellacdc/widgets.py |
Adds VisPy marker symbol combobox, gradient widget refactor, LUT state helpers, and context-menu plumbing used by the 3D renderer. |
cellacdc/volume_renderer/utils.py |
Adds voxel-size→display scaling helper for (Z,Y,X) data. |
cellacdc/volume_renderer/gl_blend.py |
Adds napari-compatible-ish GL blending state helper for multi-volume compositing. |
cellacdc/volume_renderer/canvas.py |
Implements the main VolumeRendererWindow (VisPy canvas, LUTs, labels overlay, points layers, downsampling, camera/home view). |
cellacdc/volume_renderer/_widgets.py |
Adds renderer toolbars (home/save/update + single-channel mode). |
cellacdc/volume_renderer/_demos/data/.gitkeep |
Keeps demo data folder in repo while ignoring downloaded content. |
cellacdc/volume_renderer/_demos/_demo_single_channel.py |
Demo: render a single channel from an existing dataset path. |
cellacdc/volume_renderer/_demos/_demo_single_channel_lab_3d.py |
Demo: render volume + 3D labels with voxel size. |
cellacdc/volume_renderer/_demos/_demo_single_channel_lab_3d_with_points.py |
Demo: render volume + labels + points loaded from CSV. |
cellacdc/volume_renderer/_demos/_demo_single_channel_lab_2d.py |
Demo: render 2D labels replicated along Z. |
cellacdc/volume_renderer/_demos/_demo_single_channel_custom_cmap.py |
Demo: render single channel with a custom 2-color cmap. |
cellacdc/volume_renderer/_demos/_demo_multi_channel.py |
Demo: render multi-channel overlay. |
cellacdc/volume_renderer/_demos/_demo_multi_channel_lab_3d_with_points.py |
Demo: multi-channel + labels + points. |
cellacdc/volume_renderer/_demos/_demo_lab_2d.py |
Demo: labels-only rendering. |
cellacdc/volume_renderer/__init__.py |
Exposes module-local demo data_path. |
cellacdc/plot.py |
Adds VisPy marker symbol typing + PyQtGraph↔VisPy symbol mapping/path lookup. |
cellacdc/myutils.py |
Adds helper to download and extract demo dataset. |
cellacdc/gui.py |
Integrates “Launch 3D viewer” action/button and syncs labels gradient shuffle signals. |
cellacdc/colors.py |
Adds typed colormap helpers and PyQtGraph→VisPy colormap conversion + auto-contrast percentile helper. |
cellacdc/apps.py |
Extends points-layer appearance dialog to support VisPy backend and optional opacity slider. |
cellacdc/__init__.py |
Attempts to import VolumeRendererWindow when GUI is installed (best-effort). |
.gitignore |
Ignores downloaded demo data under cellacdc/volume_renderer/_demos/data/* while keeping .gitkeep. |
Comments suppressed due to low confidence (1)
cellacdc/widgets.py:3449
ToolButtonCustomColor.paintEventalways callsp.end()infinally, butpis only defined inside thetry. IfQPainter(self)raises,finallywill raiseUnboundLocalError, masking the original exception.
def paintEvent(self, event):
QToolButton.paintEvent(self, event)
try:
p = QPainter(self)
w, h = self.width(), self.height()
sf = 0.6
p.scale(w*sf, h*sf)
p.translate(0.5/sf, 0.5/sf)
symbol = plot.PyQtGraphScatterPlotSymbolPathMatter[self.symbol]
pen = pg.mkPen(color=self.penColor, width=2)
brush = pg.mkBrush(color=self.brushColor)
p.setRenderHint(QPainter.RenderHint.Antialiasing)
p.setPen(pen)
p.setBrush(brush)
p.drawPath(symbol)
except Exception as e:
traceback.print_exc()
finally:
p.end()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1076
to
+1089
| cmaps = None | ||
| lut_items_states = None | ||
| if lut_item_state is not None: | ||
| lut_items_states = {channel_name: cmap} | ||
| elif cmap is not None: | ||
| cmaps = {channel_name: cmap} | ||
|
|
||
| volumes = {channel_name: volume} | ||
|
|
||
| self.set_volumes( | ||
| volumes, | ||
| cmaps=cmaps, | ||
| lut_items_states=lut_items_states | ||
| ) |
Comment on lines
+130
to
+134
| self._voxel_size_transform = None | ||
| self._lab_ncolors = 256 | ||
| self._voxel_size_strides_transform = None | ||
| self._downsample_strides = None | ||
| self._object_labels_list_buttongroup = None |
Comment on lines
+644
to
+654
| if self._downsample_strides is None: | ||
| strides = tuple( | ||
| max(1, int(np.ceil(s / max_size))) for s in vol.shape | ||
| ) | ||
| self._downsample_strides = strides | ||
| else: | ||
| strides = self._downsample_strides | ||
|
|
||
| if all(s == 1 for s in strides): | ||
| return vol | ||
| return np.ascontiguousarray(vol[::strides[0], ::strides[1], ::strides[2]]) |
Collaborator
Author
There was a problem hiding this comment.
While we don't guard for it, we don't allow volumes with different shapes. We will add a guard for that
Comment on lines
+1213
to
+1220
| if points_df is not None: | ||
| points_xyz = points_df[zyx_columns_names[::-1]].to_numpy() | ||
| elif points: | ||
| points_xyz = points[:, [2, 1, 0]] | ||
| else: | ||
| raise ValueError( | ||
| "Either 'points' or 'points_df' must be provided." | ||
| ) |
Comment on lines
+1356
to
+1360
| if not self._channels_data: | ||
| Z, Y, X = self._lab.shape | ||
| dummy_vol = np.zeros((Z, Y, X), dtype=np.float32) | ||
| self.set_volume(dummy_vol, channel_name='1') | ||
|
|
Comment on lines
+914
to
+919
| for c, (channel, channel_data) in enumerate(self._channels_data.items()): | ||
| blending = "translucent_no_depth" if c == 0 else "additive" | ||
| node = channel_data.node | ||
| node.order = c | ||
| node.opacity = channel_data.opacity_slider.value() | ||
| node.set_gl_state(**volume_gl_state(blending, first_visible=c==0)) |
Comment on lines
+3162
to
+3165
| if not checked: | ||
| self._volume_renderer.close() | ||
| self._volume_renderer = None | ||
| return |
Comment on lines
+3224
to
+3225
| def onClose3dViewer(self): | ||
| self.launch3dViewerAction.setChecked(False) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Taking inspiration from PRs #1110, #1102, #1096, #1106, and from https://github.com/keejkrej/acdc-son, this PR implements a 3D volume renderer that can be launched from a Python script (see below), or from the module 3 GUI.
Minimal code:
Features:
scipy.ndimage.zoom